//
//  SFCLibTests.m
//  SFCLibTests
//
//  Created by Jeremy Pereira on 06/11/2013.
//  Copyright (c) 2013 Jeremy Pereira. All rights reserved.
//
/*!
 *    @file
 *    @brief Low level tests.
 */

#import <XCTest/XCTest.h>
#import "SFCAPRPool.h"
#import "SFCError.h"
#import "SFCLib+Internals.h"
#import "SFCClient.h"
#import "SFCRevision.h"
#import "SFCTextFileDiff.h"


#define ENUM_CHECK(ME,SVN)	\
do {						\
    XCTAssertTrue((ME) == (SVN), @"%s wrong, is %d, should be %d",	\
    #ME, 															\
    (int)(ME),														\
    (int)(SVN));													\
} while(0)

#define STRING_CHECK(ME,SVN)											\
do {																	\
	NSString* meString = (ME);											\
	NSString* svnString = (SVN);										\
	XCTAssertTrue([meString isEqualToString: svnString],				\
					@"%@ is wrong, should be %@", meString, svnString);	\
} while(0)


/*!
 *    @brief General test cases for low level functionality.
 *
 *    These tests, test invidual things like constants and low level classes.
 *    @todo Test that SFCCredentials validateKind works.
 */
@interface SFCLibTests : XCTestCase

@property (readonly, copy) NSString* repoPath;
@property (readonly, copy) NSString* wcPath;

@end

@implementation SFCLibTests

@synthesize repoPath = _repoPath;
@synthesize wcPath = _wcPath;


- (void)setUp
{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each
    // test method in the class.
}


- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

-(void) testAPRPool
{
    NSError* error = nil;
	SFCAPRPool* pool = [SFCAPRPool makePoolWithParent: nil error: &error];
    if (pool == nil)
    {
        XCTFail(@"Error occurred %@", error);
    }
}

-(void) testClearSubPool
{
    NSError* error = nil;
	SFCAPRPool* pool = [SFCAPRPool makePoolWithParent: nil error: &error];
    if (pool == nil)
    {
        XCTFail(@"Error occurred %@", error);
    }
	else
    {
        SFCAPRPool* subPool = [SFCAPRPool makePoolWithParent: pool
                                                              error: &error];
		XCTAssertNotNil(subPool, @"Error %@", error);
        XCTAssertTrue([subPool parent] == pool, @"Subpool parent is wrong");
        XCTAssertTrue([pool hasChild: subPool], @"Pool child is wrong");
        [pool clear];
		XCTAssertFalse([subPool isAlive], @"sub pool should be nil");
    }
}

/*!
 *    @brief Ensure that constants are as documented.
 */
-(void) testConstants
{
	XCTAssertEqualObjects(APR_ERROR_DOMAIN, @"net.jeremyp.apr",
                          @"Invalid APR error domain %@", APR_ERROR_DOMAIN);
	XCTAssertEqualObjects(SFC_ERROR_DOMAIN, @"net.jeremyp.sfc",
                          @"Invalid SFC error domain %@", SFC_ERROR_DOMAIN);
    ENUM_CHECK(SFC_DEPTH_UNKNOWN, svn_depth_unknown);
    ENUM_CHECK(SFC_DEPTH_EXCLUDE, svn_depth_exclude);
    ENUM_CHECK(SFC_DEPTH_EMPTY, svn_depth_empty);
    ENUM_CHECK(SFC_DEPTH_FILES, svn_depth_files);
    ENUM_CHECK(SFC_DEPTH_IMMEDIATES, svn_depth_immediates);
    ENUM_CHECK(SFC_DEPTH_INFINITY, svn_depth_infinity);

    ENUM_CHECK(SFC_NODE_KIND_NONE, svn_node_none);
    ENUM_CHECK(SFC_NODE_KIND_FILE, svn_node_file);
    ENUM_CHECK(SFC_NODE_KIND_DIRECTORY, svn_node_dir);
    ENUM_CHECK(SFC_NODE_KIND_UNKNOWN, svn_node_unknown);
	ENUM_CHECK(SFC_NODE_KIND_SYM_LINK, svn_node_symlink);

    ENUM_CHECK(SFC_LPC_ACTION_ADD, 'A');
    ENUM_CHECK(SFC_LPC_ACTION_DELETE, 'D');
    ENUM_CHECK(SFC_LPC_ACTION_MODIFY, 'M');
    ENUM_CHECK(SFC_LPC_ACTION_REPLACE, 'R');

    ENUM_CHECK(SFC_WC_STATUS_NONE, svn_wc_status_none);
    ENUM_CHECK(SFC_WC_STATUS_UNVERSIONED, svn_wc_status_unversioned);
    ENUM_CHECK(SFC_WC_STATUS_NORMAL, svn_wc_status_normal);
    ENUM_CHECK(SFC_WC_STATUS_ADDED, svn_wc_status_added);
    ENUM_CHECK(SFC_WC_STATUS_MISSING, svn_wc_status_missing);
    ENUM_CHECK(SFC_WC_STATUS_DELETED, svn_wc_status_deleted);
    ENUM_CHECK(SFC_WC_STATUS_REPLACED, svn_wc_status_replaced);
    ENUM_CHECK(SFC_WC_STATUS_MODIFIED, svn_wc_status_modified);
    ENUM_CHECK(SFC_WC_STATUS_MERGED, svn_wc_status_merged);
    ENUM_CHECK(SFC_WC_STATUS_CONFLICTED, svn_wc_status_conflicted);
    ENUM_CHECK(SFC_WC_STATUS_IGNORED, svn_wc_status_ignored);
    ENUM_CHECK(SFC_WC_STATUS_OBSTRUCTED, svn_wc_status_obstructed);
    ENUM_CHECK(SFC_WC_STATUS_EXTERNAL, svn_wc_status_external);
    ENUM_CHECK(SFC_WC_STATUS_INCOMPLETE, svn_wc_status_incomplete);

    ENUM_CHECK(SFC_DIFF_IGNORE_SPACE_NONE, svn_diff_file_ignore_space_none);
    ENUM_CHECK(SFC_DIFF_IGNORE_SPACE_ALL, svn_diff_file_ignore_space_all);
    ENUM_CHECK(SFC_DIFF_IGNORE_SPACE_CHANGE, svn_diff_file_ignore_space_change);
}

-(void) testConflictChoiceValues
{
    ENUM_CHECK(SFC_CONFLICT_CHOICE_POSTPONE, svn_wc_conflict_choose_postpone);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_BASE, svn_wc_conflict_choose_base);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_MINE_FULL, svn_wc_conflict_choose_mine_full);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_THEIRS_FULL, svn_wc_conflict_choose_theirs_full);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_MERGED, svn_wc_conflict_choose_merged);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_THEIRS_CONFLICT, svn_wc_conflict_choose_theirs_conflict);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_MINE_CONFLICT, svn_wc_conflict_choose_mine_conflict);
    ENUM_CHECK(SFC_CONFLICT_CHOICE_UNSPECIFIED, svn_wc_conflict_choose_unspecified);

}

-(void) testConflictKindValues
{
    ENUM_CHECK(SFC_CONFLICT_KIND_PROPERTY, svn_wc_conflict_kind_property);
    ENUM_CHECK(SFC_CONFLICT_KIND_TEXT, svn_wc_conflict_kind_text);
    ENUM_CHECK(SFC_CONFLICT_KIND_TREE, svn_wc_conflict_kind_tree);
}

-(void) testStatusKindConversion
{
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_ADDED], @"A");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_CONFLICTED], @"C");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_DELETED], @"D");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_IGNORED], @"I");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_MODIFIED], @"M");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_REPLACED], @"R");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_EXTERNAL], @"X");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_UNVERSIONED], @"?");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_MISSING], @"!");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_OBSTRUCTED], @"~");
    STRING_CHECK([SFCClient stringForStatus: SFC_WC_STATUS_NORMAL], @"n");
}

-(void) testConflictActionValues
{
    ENUM_CHECK(SFC_CONFLICT_ACTION_EDIT, svn_wc_conflict_action_edit);
    ENUM_CHECK(SFC_CONFLICT_ACTION_ADD, svn_wc_conflict_action_add);
    ENUM_CHECK(SFC_CONFLICT_ACTION_DELETE, svn_wc_conflict_action_delete);
    ENUM_CHECK(SFC_CONFLICT_ACTION_REPLACE, svn_wc_conflict_action_replace);
}

-(void) testConflictReasonValues
{
    ENUM_CHECK(SFC_CONFLICT_REASON_EDITED, svn_wc_conflict_reason_edited);
    ENUM_CHECK(SFC_CONFLICT_REASON_OBSTRUCTED, svn_wc_conflict_reason_obstructed);
    ENUM_CHECK(SFC_CONFLICT_REASON_DELETED, svn_wc_conflict_reason_deleted);
    ENUM_CHECK(SFC_CONFLICT_REASON_MISSING, svn_wc_conflict_reason_missing);
    ENUM_CHECK(SFC_CONFLICT_REASON_UNVERSIONED, svn_wc_conflict_reason_unversioned);
    ENUM_CHECK(SFC_CONFLICT_REASON_ADDED, svn_wc_conflict_reason_added);
    ENUM_CHECK(SFC_CONFLICT_REASON_REPLACED, svn_wc_conflict_reason_replaced);
    ENUM_CHECK(SFC_CONFLICT_REASON_MOVED_AWAY, svn_wc_conflict_reason_moved_away);
    ENUM_CHECK(SFC_CONFLICT_REASON_MOVED_HERE, svn_wc_conflict_reason_moved_here);

}

-(void) testOperationValues
{
    ENUM_CHECK(SFC_OPERATION_NONE, svn_wc_operation_none);
    ENUM_CHECK(SFC_OPERATION_UPDATE, svn_wc_operation_update);
    ENUM_CHECK(SFC_OPERATION_SWITCH, svn_wc_operation_switch);
    ENUM_CHECK(SFC_OPERATION_MERGE,  svn_wc_operation_merge);
}

-(void) testGetSvnString
{
    NSError* error = nil;
    SFCAPRPool* pool = [SFCAPRPool makePoolWithParent: nil error: &error];
    if (pool == nil)
    {
        @throw [NSException exceptionWithName: @"TestGeySvnString"
                                       reason: [error localizedDescription]
                                     userInfo: nil];
    }
	svn_string_t* svnString = [@"foobar" svnStringWithPool: pool];
    XCTAssertTrue(strncmp(svnString->data, "foobar", svnString->len) == 0,
                  @"String fails to compare correctly");
}

-(void) testMakeStringFromSvnString
{
    NSError* error = nil;
    SFCAPRPool* pool = [SFCAPRPool makePoolWithParent: nil error: &error];
    if (pool == nil)
    {
        @throw [NSException exceptionWithName: @"testMakeStringFromSvnString"
                                       reason: [error localizedDescription]
                                     userInfo: nil];
    }
	svn_string_t* str = [@"foobar" svnStringWithPool: pool];
    if (str == NULL)
    {
        XCTFail(@"Failed to create svn string");
    }
    else
    {
        NSString* result = [NSString stringWithSvnString: str];
        XCTAssertTrue([result isEqualToString: @"foobar"], @"NSString is wrong");
    }
}

-(void) testSFCRevision
{
    SFCRevision* rev = [SFCRevision unspecified];
    ENUM_CHECK([rev kind], svn_opt_revision_unspecified);

    rev = [SFCRevision number: 3];
    ENUM_CHECK([rev kind], svn_opt_revision_number);
    XCTAssertTrue([rev number] == 3, @"Invalid revision %@", rev);
    NSDate* theDate = [NSDate date];

    rev = [SFCRevision date: theDate];
    ENUM_CHECK([rev kind], svn_opt_revision_date);
    XCTAssertTrue([[rev date] isEqualTo: theDate], @"Invalid revision %@", rev);

    rev = [SFCRevision committed];
    ENUM_CHECK([rev kind], svn_opt_revision_committed);
    rev = [SFCRevision previous];
    ENUM_CHECK([rev kind], svn_opt_revision_previous);
    rev = [SFCRevision base];
    ENUM_CHECK([rev kind], svn_opt_revision_base);
    rev = [SFCRevision working];
    ENUM_CHECK([rev kind], svn_opt_revision_working);
    rev = [SFCRevision head];
    ENUM_CHECK([rev kind], svn_opt_revision_head);
}

static NSString* const TEST_DIFF_STRING = @""
"@@ -624,7 +624,7 @@\n"
" \n"
" -(IBAction) testParseArray: (id) sender\n"
" {\n"
"-	RMJsonParser* parser = [[RMJsonParser alloc] init];\n"
"+	RMJsonJPParser* parser = [[RMJsonJPParser alloc] init];\n"
" 	NSString* testString = @\"[1, 2, \"foo\", 3]\";\n"
" 	@try\n"
" 	{\n";
enum { WRITE_BLOCK_SIZE = 20 };

-(void) testDiffParsing
{
	SFCDiffCollection* diff = [[SFCDiffCollection alloc] init];
    NSData* testStringAsData = [TEST_DIFF_STRING dataUsingEncoding: NSUTF8StringEncoding];
    NSOutputStream* stream = [diff outputStream];
    /*
     *  Write the test file in chunks to simulate getting it in chunks as in a 
     *  pipe.
     */
    size_t bytesToWrite = [testStringAsData length];
    const uint8_t* bufferLocation = [testStringAsData bytes];
    NSInteger bytesWritten = 0;
    while (bytesToWrite > 0 && bytesWritten != -1)
    {
        bytesWritten = [stream write: bufferLocation maxLength: bytesToWrite];
        if (bytesWritten > 0)
        {
            bytesToWrite -= bytesWritten;
            bufferLocation += bytesWritten;
        }
    }
	XCTAssertTrue(bytesWritten != -1, @"Write failure %@", [stream streamError]);
    XCTAssertTrue([diff diffCount] == 1, @"Diff has incorrect number of files");
    if ([diff diffCount] == 1)
    {
        SFCTextFileDiff* fileDiff = [diff diffAtIndex: 0];
        XCTAssertTrue([fileDiff hunkCount] == 1, @"Diff has incorrect hunk count");
        XCTAssertTrue([[fileDiff hunkAtIndex: 0] lineCount] == 8,
                      @"Hunk has incorrect line count");
    }
}

-(void) testAbsolutePath
{
    NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath];
    if ([cwd hasPrefix: @"/private"])
    {
        cwd = [cwd substringFromIndex: [@"/private" length]];
    }
    NSString* test = @"foo";
    XCTAssertTrue([[test absolutePath] isEqualToString: [cwd stringByAppendingPathComponent: test]],
                  @"Absolute path gave wrong path, %@, expecting %@",
                  [test absolutePath],
                  [cwd stringByAppendingPathComponent: test]);
    NSString* testAbsolute = @"/foo";
    XCTAssertTrue([[testAbsolute absolutePath] isEqualToString: testAbsolute],
                  @"Absolute path gave wrong path, %@, expecting %@",
                  [testAbsolute absolutePath],
                  testAbsolute);
}

#define IS_SIGNED_TYPE(T) (((T) -1) < ((T) 1))

-(void) testSFCRevNum
{
	XCTAssertTrue(sizeof(SFCRevNum) == sizeof(svn_revnum_t),
                  @"Our revnum is the wrong size, %lu, should be %lu",
                  sizeof(SFCRevNum),
                  sizeof(svn_revnum_t));
    XCTAssertTrue((IS_SIGNED_TYPE(SFCRevNum) && IS_SIGNED_TYPE(svn_revnum_t)) ||(!IS_SIGNED_TYPE(SFCRevNum) && !IS_SIGNED_TYPE(svn_revnum_t)),
                  @"Signed sematics for SFCRevnNum are wrong");
}

@end
